// Copyright(c) 2008 Tri Tech Information Systems Inc. 
// Distributed under the Boost Software License, Version 1.0.
//     (See accompanying file ../../LICENSE_1_0.txt or copy at
//           http://www.boost.org/LICENSE_1_0.txt)
//     

#ifndef __UTIL_PYTHON_ERROR_H__
#define __UTIL_PYTHON_ERROR_H__

#include "python_gil.h"
#ifndef BUILDING_SIP_BINDINGS
#include <boost/python.hpp>
#endif
#include <iostream>

struct ErrorApi
{
    virtual void raise(
        boost::python::object, 
        boost::python::object,
        boost::python::object,
        bool) = 0;

    static ErrorApi * get()
    {
        using namespace boost::python;
        static ErrorApi * api = 0;
        if(!api)
        {
            object api_obj = import("pyxx_helper").attr("_error_api");
            api = reinterpret_cast<ErrorApi*>(PyCObject_AsVoidPtr(api_obj.ptr()));
        }
        return api;
    }
};



struct IPythonError
{
    virtual void restore() = 0;
};

struct python_error :
    public IPythonError,
    public std::runtime_error
{
    /**
      * Perform the work needed to actually throw this exception
      *
      * NB: Calling this method will clear the Python error indicators
      */
    static python_error raise(bool throw_cpp_error = true)
    {
        using namespace boost::python;
        PythonGIL gil;
        
        PyObject* type_;
        PyObject* val_;
        PyObject* tb_;


        PyErr_Fetch( &type_, &val_, &tb_ );


        object type = object( handle<>(type_) );
        object val = object( handle<>(val_) );
        object tb;
        if(tb_)
           tb = object( handle<>(tb_));



        ErrorApi::get()->raise(type, val, tb, throw_cpp_error);
        throw 0;

        // an obnoxious "feature" of icl is that it will not let you skip out
        // on having a return value. I much before dmd's approach of asserting thing
        // at any rate, the problem comes because function likes this always throw
        // but the compiler does not know that. 

        // accordingly, we advertise that this function returns a python error.
        // hence we call it: throw python_error::raise() which will let the compiler know
        // this it will always throw

    }

    /**
      * Raise a python error if PyErr_Occurred() is true
      */
    static void check()
    {
        if( PyErr_Occurred() )
            raise();
    }

    /**
      * Restore this exception to the Python error indicators
      *
      * NB: Calling this method clears the actual python exception
      *  context from this object
      */
    void restore()
    {
        PythonGIL gil;
        boost::python::incref(m_type.ptr());
        boost::python::incref(m_value.ptr());
        boost::python::incref(m_traceback.ptr());
        PyErr_Restore( m_type.ptr(), m_value.ptr(), m_traceback.ptr() );
    }

    virtual ~python_error() throw()
    {
    }

    python_error( boost::python::object type, boost::python::object val, boost::python::object tb ) :
        std::runtime_error( format_error(type, val, tb) ),
        m_type( type ),
        m_value( val ),
        m_traceback( tb )
    {
    }


private:
    boost::python::object m_type;
    boost::python::object m_value;
    boost::python::object m_traceback;

    // TODO: This should really not live in a header
    static std::string format_error( boost::python::object type, boost::python::object val, boost::python::object tb )
    {
        using namespace boost::python;
        try
        {
            object traceback = import( str("traceback") );
            object StringIO = import( str("StringIO") ).attr("StringIO");
            object output = StringIO();
            traceback.attr("print_exception")(type, val, tb, object(), output);
            return extract<std::string>(output.attr("getvalue")());            
        }
        catch(error_already_set &)
        {
            // zounds! we had errors in the error reporting code!
        
            PyObject * new_type;
            PyObject * new_val;
            PyObject * new_tb;
            PyErr_Fetch( &new_type, &new_val, &new_tb );
            PyObject * err = PyObject_Str(new_val);
            std::string error = PyString_AsString( err);
            Py_DECREF(err);
            Py_XDECREF(new_type);
            Py_XDECREF(new_val);
            Py_XDECREF(new_tb);
            return error;
        }    
    }

};


#endif
